#include <retro.h>
#include "retro.library.h"

using namespace Retro;
using namespace Retro::Device;
using namespace Retro::Device::Display;

VOID  WriteSpriteNode(
  Device::Display::SpriteNode* value,
  Data::SpriteData* &dest
  )
{
  SpriteNode* child = value->child;
  Data::SpriteData* last;

  while (1)
  {
    last = dest;
    memcpy(dest++, child, sizeof(Data::SpriteData));

    if (child == value->child)
      last->flags |= SPRITEDATA_CHILD;
    if (child->child)
      WriteSpriteNode(child, dest);

    if (child->next)
    {
      child = child->next;
    }
    else
    {
      last->flags |= SPRITEDATA_LASTCHILD;
      break;
    }
  }
}

BOOL  Display::DrawSpriteNode(
  DisplayDevice* iDevice,
  const SpriteNode* iNode,
  const Geom::Bounds& iDest,
  const POINT& iOffset
  )
{
  Geom::Point translate = { iOffset.x + iDest.x, iOffset.y + iDest.y };

  if (iNode->flags & SPRITEDATA_GROUP)
  {
    SpriteNode* child = iNode->child;

    while (child)
    {
      FCHECK(DrawSpriteNode(
        iDevice,
        child,
        iNode->viewBounds.point + Geom::Point::get(
        (LONG)(child->bounds.x * iNode->scaleX),
        (LONG)(child->bounds.y * iNode->scaleY)) + Geom::Size::get(
        (LONG)(child->bounds.cx * iNode->scaleX),
        (LONG)(child->bounds.cy * iNode->scaleY)),
        iOffset));

      child = child->next;
    }
  }
  else if (iNode->flags & SPRITEDATA_TILE)
  {
    Geom::Size size = { abs(iDest.cx), abs(iDest.cy) };
    Geom::Size tile = { iDest.cx < 0 ? -1 : 1, iDest.cy < 0 ? -1 : 1 };

    INT x = 0;

    while (x < size.cx)
    {
      INT y = 0;

      while (y < size.cy)
      {
        Geom::Size source = {
          min(iNode->source.cx, size.cx - x),
          min(iNode->source.cy, size.cy - y)
        };

        FCHECK(iDevice->beginBatch(iNode->texture));

        iDevice->addBatch(
          Geom::Point::get(
          translate.x + x * tile.cx,
          translate.y + y * tile.cy),
          &Geom::Bounds::get(iNode->source.point, source),
          0xFFFFFFFF);

        y += iNode->source.cy;
      }

      x += iNode->source.cx;
    }
  }
  else
  {
    Vector::Vector2D scale(
      (REAL)iDest.cx / iNode->source.cx,
      (REAL)iDest.cy / iNode->source.cy);
    Geom::Bounds dest;

    if (iNode->viewRotate)
    {
      iDevice->end();

      iDevice->transform(
        Vector::Vector2D(
          0.0f,
          0.0f),
        Vector::Vector2D(
          1.0f,
          1.0f),
        Vector::Vector2D(
          (REAL)iDest.cx / 2,
          (REAL)iDest.cy / 2),
        iNode->viewRotate,
        Vector::Vector2D(
          (REAL)translate.x,
          (REAL)translate.y));

      dest = Geom::Bounds::get(0, 0, iDest.cx, iDest.cy);
    }
    else
    {
      dest = Geom::Bounds::get(translate.x, translate.y, iDest.cx, iDest.cy);
    }

    switch (iNode->type)
    {
    case Data::SpriteTexture:
      FCHECK(iDevice->beginBatch(iNode->texture));

      iDevice->addBatch(dest, &iNode->source, 0xFFFFFFFF);
      break;
    case Data::SpriteRect:
      FCHECK(iDevice->beginBatch(NULL));

      iDevice->addBatch(dest, NULL,
        iNode->colorTL.value,
        iNode->colorTR.value,
        iNode->colorBL.value,
        iNode->colorBR.value);
      break;
    }

    if (iNode->viewRotate)
    {
      iDevice->end();
      iDevice->resetTransform();
    }
  }

  return TRUE;
}

VOID  Display::GetSpriteNodes(
  const Data::SpriteMap* iMap,
  SpriteNode** iBuffer
  )
{
  for (int i = 0; i < iMap->getLayers(); ++i)
  {
    SpriteNode** node = &iBuffer[i];
    SpriteNode* prev = NULL;
    SpriteNode* parent = NULL;

    for (int j = 0; j < iMap->buffer[i].header.length; ++j)
    {
      if (iMap->buffer[i].sprites[j].flags & SPRITEDATA_CHILD)
      {
        parent = prev;
        node = &parent->child;
      }

      *node = new SpriteNode;

      memset(
        *node,
        0,
        sizeof(SpriteNode));
      memcpy(
        *node,
        &iMap->buffer[i].sprites[j],
        sizeof(Data::SpriteData));

      (*node)->viewBounds = (*node)->bounds;
      (*node)->viewRotate = (*node)->rotate;
      (*node)->prev       = prev;
      (*node)->child      = NULL;
      //(*node)->flags     &= ~(SPRITENODE_HIDDEN | SPRITENODE_LOCKED);

      if (iMap->buffer[i].sprites[j].flags & SPRITEDATA_LASTCHILD)
      {
        prev = parent;
        node = &parent->next;
      }
      else
      {
        prev = *node;
        node = &(*node)->next;
      }
    }
  }
}

VOID  Display::SetSpriteNodes(
  Data::SpriteMap* iMap,
  SpriteNode** iBuffer
  )
{
  for (int i = 0; i < iMap->getLayers(); ++i)
  {
    SpriteNode* node = iBuffer[i];
    Data::SpriteData* ptr;

    iMap->buffer[i].header.length = 0;

    while (node)
    {
      ++iMap->buffer[i].header.length;
      iMap->buffer[i].header.length += GetSpriteChildCount(node, TRUE);

      node = node->next;
    }

    if (iMap->buffer[i].sprites)
      delete[] iMap->buffer[i].sprites;

    if (iMap->buffer[i].header.length)
    {
      iMap->buffer[i].sprites =
        new Data::SpriteData[iMap->buffer[i].header.length];

      node = iBuffer[i];
      ptr = iMap->buffer[i].sprites;

      while (node)
      {
        memcpy(ptr++, node, sizeof(Data::SpriteData));
        if (node->child) WriteSpriteNode(node, ptr);

        node = node->next;
      }
    }
    else
    {
      iMap->buffer[i].sprites = NULL;
    }
  }
}

INT   Display::GetSpriteChildCount(
  const Data::SpriteData* value,
  BOOL recursive
  )
{
  SpriteNode* child = ((SpriteNode*)value)->child;
  INT count = 0;

  while (child) {
    if (recursive && (child->flags & SPRITEDATA_GROUP))
      count += GetSpriteChildCount(child, TRUE);
    else
      ++count;

    child = child->next;
  }

  return count;
}

VOID  Display::DeleteSpriteNode(SpriteNode* value)
{
  while (value)
  {
    SpriteNode* next = value->next;

    DeleteSpriteNode(value->child);
    delete value;

    value = next;
  }
}

VOID  Display::InitializeSpriteNodes(
  DisplayDevice* iDevice,
  SpriteNode** iBuffer,
  INT iLayers,
  const Data::RetroProject* iProject
  )
{
  for (INT i = 0; i < iLayers; ++i)
  {
    SpriteNode* wNode = iBuffer[i];

    while (wNode)
    {
      Data::Resource* wResource;

      switch (wNode->type)
      {
      case Data::SpriteTexture:
        if (wResource = iProject->findResource(
          Data::ResourceTexture, wNode->id))
        {
          wNode->texture = iDevice->createTexture(
            ((Data::Texture*)wResource)->getPath());
        }
        break;
      }

      wNode = wNode->next;
    }
  }
}
